package pers.cz.orm.dao.processor;

import pers.cz.common.annotation.Column;
import pers.cz.common.annotation.Ignore;
import pers.cz.common.annotation.Rule;
import pers.cz.common.annotation.Sharding;
import pers.cz.common.pub.*;
import pers.cz.common.sharding.IShardingStrategy;
import pers.cz.common.sharding.Ruling;
import pers.cz.common.sharding.ShardingRule;
import pers.cz.orm.dao.dialect.AbstractDialect;
import pers.cz.orm.dao.dialect.DbDialect;
import pers.cz.common.meta.TableMeta;
import pers.cz.orm.enums.DbConfigItem;
import utils.JefLog;
import pers.cz.jefconfig.config.JefConfiguration;
import utils.ObjUtils;

import java.util.*;

/**
 * @program: jef-orm
 * @description: 抽象增删改查操作
 * @author: Cheng Zhi
 * @create: 2023-08-10 12:13
 **/
public abstract class AbstractDbProcessor implements DbProcessor {

    JefLog jefLog = new JefLog(AbstractDbProcessor.class);

    public static final String ALL = "*";
    public static final String AND = " and ";
    public static final String WHERE = " where ";
    public static final String EQUALS = " = ";
    public static final String FROM = " from ";
    public static final String ENDSYMBOL = "; ";
    public static final String SPACE = " ";
    public static final String COMMA = ", ";
    public static final String SET = " set ";
    public static final String LEFT_PAREN = "( ";
    public static final String RIGHT_PAREN = ") ";


    private DbProcessorEnums operEnum;

    protected DbDialect dialect;

    private String tableName;

    private TableMeta tableMeta;

    /**
     * 分表规则计算实现
     */
    private IShardingStrategy shardingStrategy;

    protected AbstractEntity entity;

    public AbstractEntity getEntity() {
        return entity;
    }

    public void setEntity(AbstractEntity entity) {
        this.entity = entity;
    }

    public DbProcessorEnums getOperEnum() {
        return operEnum;
    }

    public void setOperEnum(DbProcessorEnums operEnum) {
        this.operEnum = operEnum;
    }

    public String getTableName() {
        return tableName;
    }

    public Map<String, Column> getColums() {
        return tableMeta.getColumns();
    }

    public Map<String, Ignore> getIgnores() {
        return tableMeta.getIgnores();
    }

    public void setTableMeta(TableMeta tableMeta) {
        this.tableMeta = tableMeta;
        this.tableName = tableMeta.getTable().name();
    }

    public IShardingStrategy getShardingStrategy() {
        return shardingStrategy;
    }

    public void setShardingStrategy(IShardingStrategy shardingStrategy) {
        this.shardingStrategy = shardingStrategy;
    }

    public String getSql() {

        // 生成sql的时候获取注册数据库方言
        registerDialect();

        String sql = bindSql();
        jefLog.info("SHOW SQL:" + sql);
        return sql;
    }

    private void registerDialect() {
        dialect = AbstractDialect.getCurrentDbDialect(JefConfiguration.get(DbConfigItem.MASTER_DB_URL));
    }

    public abstract String bindSql();


    /**
     * 获取其他非实体绑定的查询条件，比如getQuery.addCondition()和getQuery.bindOtherFianlCondition()传入的参数。
     * @return
     */
    private String getQueryCondition() {

        StringBuilder bindSql = new StringBuilder();
        Query query = entity.getQuery();
        ShardingRule shardingRule = entity.getShardingRule();
        List<Condition> conditions = query.getConditions();
        if (conditions != null && !conditions.isEmpty()) {
            int andCount = 0;
            for (Condition condition : conditions) {
                Operator operator = condition.getOperator();
                andCount ++;
                boolean isNeedAnd = true;
                // 计算分表名
                calculateSharding(ObjUtils.under2camel(condition.getColumnName()), condition.getBindVal());
                Column column = tableMeta.getColumns().get(ObjUtils.under2camel(condition.getColumnName()));
                String columnName = column == null? condition.getColumnName() : column.name();
                // 这里不需要判断忽略字段，因为查询条件中不可能有忽略字段
                switch (operator) {
                    case OR: // or 需要特殊处理
                        bindSql.append(operator.getOpName());
                        bindSql.append(columnName);
                        bindSql.append(EQUALS);
                        bindSql.append(formatVal(condition.getBindVal()));
                        break;
                    case IN:
                        bindSql.append(columnName);
                        bindSql.append(operator.getOpName());
                        bindSql.append(LEFT_PAREN);
                        if (condition.getBindVal() instanceof List) {
                            int sysbolCount = 0;
                            for (Object o : (List)condition.getBindVal()) {
                                sysbolCount ++;
                                bindSql.append(formatVal(o));
                                if (sysbolCount != ((List) condition.getBindVal()).size()) {
                                    bindSql.append(COMMA);
                                }
                            }
                        } else if(condition.getBindVal().getClass().isArray()) {
                            int sysbolCount = 0;
                            List<Object> objects = Arrays.asList(condition.getBindVal());
                            for (Object o : objects) {
                                sysbolCount ++;
                                bindSql.append(formatVal(o));
                                if (sysbolCount != objects.size()) {
                                    bindSql.append(COMMA);
                                }
                            }
                        } else {
                            bindSql.append(formatVal(condition.getBindVal()));
                        }
                        bindSql.append(RIGHT_PAREN);
                        break;
                    default:
                        bindSql.append(columnName);
                        bindSql.append(operator.getOpName());
                        bindSql.append(formatVal(condition.getBindVal()));
                        break;
                }

                if (isNeedAnd && andCount != conditions.size()) {
                    bindSql.append(AND);
                }
            }
        }
        // 补充开发者强制指定的分表内容
        calculateShardingForce(shardingRule);
        String otherBindFinalCondition = query.getOtherBindFinalCondition();
        if (otherBindFinalCondition != null && !otherBindFinalCondition.isEmpty()) {
            bindSql.append(otherBindFinalCondition);
        }
        return bindSql.toString();
    }

    /**
     * 将查询条件的值转换为数据库可以识别的的值
     * @param value
     * @return
     */
     Object formatVal(Object value) {

        if (value instanceof Integer) {
            return value;
        } else if (value instanceof Double) {
            return value;
        } else if (value instanceof Float) {
            return value;
        } else if (value instanceof Long) {
            return value;
        } else if (value instanceof Character) {
            return toDbVar(value);
        } else if (value instanceof String) {
            return toDbVar(value);
        } else if (value instanceof Date) {
            String oracleDate = dialect.toDbDate((Date) value);
            return oracleDate;
        }

        return null;
    }

    public String getWhereClause() {

         // 处理映射字段
        StringBuilder sqlBuilder = new StringBuilder();
        String queryCondition = getQueryCondition();
        if (queryCondition != null && !queryCondition.isEmpty()) {
            sqlBuilder.append(WHERE);
        }
        sqlBuilder.append(queryCondition);
        return sqlBuilder.toString();
    }

    private String toDbVar(Object object) {
        return "'" + object + "'";
    }

    protected void calculateSharding(String columnName, Object value) {

        Sharding sharding = tableMeta.getSharding();
        if (sharding != null) {
            Rule[] rules = sharding.rules();
            for (Rule rule : rules) {
                if (rule.key().equals(columnName)) {
                    // 分表字段匹配上，需要进行分表
                    String result = shardingStrategy.getResult(rule, value);
                    tableName = tableName + sharding.appender() + result;
                }
            }
        }
    }

    /**
     * 强制指定分表，一般用于分表字段不是条件的场景，比如：select * from test_1_x where code = 1001; x的分表规则指定的是name
     * 此时需要使用 xxEntity.getShardingRule.addShardignRule(name, ShardingStrategy.xx);
     * @param shardingRule
     */
    protected void calculateShardingForce(ShardingRule shardingRule) {

        if (shardingRule != null) {
            List<Ruling> shardingRules = shardingRule.getShardingRule();
            if (shardingRules != null) {
                for (Ruling ruling : shardingRules) {
                    String result = shardingStrategy.getResult(ruling.getShardingStrategy(), ruling.getModulus(), ruling.getValue());
                    tableName = tableName + ruling.getAppender() + result;
                }
            }
        }
    }
}
